/*++

Copyright (c) Microsoft Corporation.  All rights reserved.

    THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
    KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
    PURPOSE.

Module Name:

    S5933DK1.c - Driver for the S5933DK1 ISA adapter.

Abstract:

Environment:

    Kernel mode

--*/

#include "common.h"
#include "public.h"
#include "S5933DK1.h"

//
// The trace message header file must be included in a source file
// before any WPP macro calls and after defining a WPP_CONTROL_GUIDS
// macro. During the compilation, WPP scans the source files for
// TraceEvents() calls and builds a .tmh file which stores a unique
// data GUID for each message, the text resource string for each message,
// and the data types of the variables passed in for each message.
// This file is automatically generated and used during post-processing.
//
#include "S5933DK1.tmh"


//
// Make sure the initialization code is removed from memory after use.
//
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, AmccIsaEvtDeviceAdd)
#pragma alloc_text(PAGE, AmccIsaEvtDevicePrepareHardware)
#pragma alloc_text(PAGE, AmccIsaEvtDeviceReleaseHardware)
#pragma alloc_text(PAGE, AmccIsaFreeDeviceResources)
#pragma alloc_text(PAGE, AmccIsaEvtIoDeviceControl)
#pragma alloc_text(PAGE, AmccIsaWorker)
#pragma alloc_text(PAGE, AmccIsaResetDevice)
#endif



NTSTATUS
AmccIsaEvtDeviceAdd(
    __in    WDFDRIVER        Driver,
    __inout PWDFDEVICE_INIT  DeviceInit
    )
/*++

Routine Description:

Arguments:

Return Value:

    NTSTATUS

--*/
{
    WDF_PNPPOWER_EVENT_CALLBACKS    pnpPowerCallbacks;
    NTSTATUS                   status = STATUS_SUCCESS;
    WDF_OBJECT_ATTRIBUTES       fdoAttributes;
    WDFDEVICE                   device;
    WDF_IO_QUEUE_CONFIG         ioQueueConfig;
    PRDK_DEVICE_EXTENSION           devExt;
    WDFQUEUE                    hQueue;

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, AMCC_TRACE_INIT,
                        "AmccIsaEvtDeviceAdd: 0x%p", Driver);

    //
    // Zero out the pnpPowerCallbacks structure.
    //
    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);

    //
    // Set Callbacks for any of the functions we are interested in.
    // If no callback is set, Framework will take the default action
    // by itself.
    //
    pnpPowerCallbacks.EvtDevicePrepareHardware = AmccIsaEvtDevicePrepareHardware;
    pnpPowerCallbacks.EvtDeviceReleaseHardware = AmccIsaEvtDeviceReleaseHardware;

    //
    // Register the Callbacks.
    //
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

    //
    // Set various attributes & characteristics for this device
    //
    WdfDeviceInitSetIoType( DeviceInit, WdfDeviceIoDirect );

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&fdoAttributes, RDK_DEVICE_EXTENSION);

    status = WdfDeviceCreate( &DeviceInit, &fdoAttributes, &device );

    if ( !NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_INFORMATION, AMCC_TRACE_INIT,
                            "WdfDeviceInitialize failed 0x%X", status);
        return status;
    }

    //
    // Device Initialization is complete.
    // Get the Device Extension and initialize it.
    //

    devExt = AmccIsaGetDevExt(device);

    devExt->Device = device;

    TraceEvents(TRACE_LEVEL_INFORMATION, AMCC_TRACE_INIT,
                "PDO 0x%p, FDO 0x%p, DevExt 0x%p",
                WdfDeviceWdmGetPhysicalDevice(device),
                WdfDeviceWdmGetDeviceObject( device ), devExt);

    //
    // Register I/O callbacks.
    //
    // Create a sequential queue so that requests are dispatched to the
    // driver one at a time. Until the driver completes a dispatched request another
    // request will be not presented to the driver.
    // A default queue gets all the requests that are not
    // configure-fowarded using WdfDeviceConfigureRequestDispatching.
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE( &ioQueueConfig,
                              WdfIoQueueDispatchSequential);

    ioQueueConfig.EvtIoDeviceControl = AmccIsaEvtIoDeviceControl;

    status = WdfIoQueueCreate( device,
                                          &ioQueueConfig,
                                          WDF_NO_OBJECT_ATTRIBUTES,
                                          &hQueue );

    if (!NT_SUCCESS (status)) {
        TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                            "WdfIoQueueCreate failed 0x%X\n", status);
        return status;
    }

    //
    // Register an interface so that application can find and talk to us.
    // NOTE: See the note in Public.h concerning this GUID value.
    //
    status = WdfDeviceCreateDeviceInterface( device,
                                             (LPGUID) &GUID_DEVINTERFACE_AMCC_ISA,
                                             NULL );

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                    "<-- AMCCAddDevice: WdfDeviceCreateDeviceInterface failed 0x%X", status);
        return status;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, AMCC_TRACE_INIT,
                "<--exit 0x%X", status);

    return status;
}

NTSTATUS
AmccIsaEvtDevicePrepareHardware(
    __in WDFDEVICE       Device,
    __in WDFCMRESLIST   ResourcesRaw,
    __in WDFCMRESLIST   ResourcesTranslated
    )
/*++

Routine Description:

    EvtDevicePrepareHardware event callback performs operations that are necessary
    to make the driver's device operational. The framework calls the driver's
    EvtDevicePrepareHardware callback when the PnP manager sends an
    IRP_MN_START_DEVICE request to the driver stack.

Arguments:

    Device - Handle to a framework device object.

    Resources - Handle to a collection of framework resource objects.
                This collection identifies the raw (bus-relative) hardware
                resources that have been assigned to the device.

    ResourcesTranslated - Handle to a collection of framework resource objects.
                This collection identifies the translated (system-physical)
                hardware resources that have been assigned to the device.
                The resources appear from the CPU's point of view.
                Use this list of resources to map I/O space and
                device-accessible memory into virtual address space

Return Value:

    WDF status code.

    Let us not worry about cleaning up the resources here if we fail start,
    because the PNP manager will send a remove-request and we will free all
    the allocated resources in AmccIsaFreeDeviceResources.
--*/
{
    ULONG               i;
    PRDK_DEVICE_EXTENSION   devExt         = NULL;
    BOOLEAN             foundPort300   = FALSE;
    BOOLEAN             foundPort718   = FALSE;
    PHYSICAL_ADDRESS    port300BasePA  = {0};
    PHYSICAL_ADDRESS    port718BasePA  = {0};
    ULONG               port300Count   = 0;
    ULONG               port718Count   = 0;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR  desc;

    PAGED_CODE();

    UNREFERENCED_PARAMETER( ResourcesRaw );


    devExt = AmccIsaGetDevExt(Device);

    //
    // Parse the resource list and save the resource information.
    //
    for (i=0; i < WdfCmResourceListGetCount(ResourcesTranslated); i++) {

        desc = WdfCmResourceListGetDescriptor(ResourcesTranslated, i);

        if(!desc) {

            TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                                "WdfResourceCmGetDescriptor failed");

            return STATUS_DEVICE_CONFIGURATION_ERROR;
        }

        switch (desc->Type) {

        case CmResourceTypePort:

            TraceEvents(TRACE_LEVEL_INFORMATION, AMCC_TRACE_INIT,
                                "Found Port Resource [%X-%X]",
                                desc->u.Port.Start.LowPart,
                                desc->u.Port.Start.LowPart +
                                desc->u.Port.Length );

            //
            // First port resource should be for ports 300-340
            //
            if (foundPort300 == FALSE) {

                port300BasePA = desc->u.Port.Start;
                port300Count  = desc->u.Port.Length;
                devExt->Port300Mapped =
                    ((desc->Flags & CM_RESOURCE_PORT_IO) == CM_RESOURCE_PORT_IO) ?
                        TRUE : FALSE;

                foundPort300 = TRUE;
                break;
            }

            //
            // Second port resource should be for ports 718-720
            //
            if (foundPort718 == FALSE) {

                port718BasePA = desc->u.Port.Start;
                port718Count  = desc->u.Port.Length;
                devExt->Port718Mapped =
                    ((desc->Flags & CM_RESOURCE_PORT_IO) == CM_RESOURCE_PORT_IO) ?
                        TRUE : FALSE;

                foundPort718 = TRUE;
                break;
            }

            ASSERT(!"unexpected port resource");
            break;

            default:
                TraceEvents(TRACE_LEVEL_WARNING, AMCC_TRACE_INIT,
                                    "Used resource type %d", desc->Type);
                break;
        }
    }

    if (!foundPort300 && !foundPort718) {

        TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                            "Missing expected hardware resources");

        return STATUS_DEVICE_CONFIGURATION_ERROR;
    }

    //
    // Map in the 300-340 port IO Space resource.
    //
    if (devExt->Port300Mapped) {

        devExt->Port300Base =
            (PUCHAR) MmMapIoSpace( port300BasePA, port300Count, MmNonCached );

        if (!devExt->Port300Base) {

            TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                                "Unable to map port range [%X-%X]",
                                (ULONG) (ULONG_PTR) devExt->Port300Base,
                                (ULONG) devExt->Port300Count );

            return STATUS_INSUFFICIENT_RESOURCES;
        }

        devExt->Port300Count = port300Count;

    } else {

        devExt->Port300Base  = (PUCHAR)(ULONG_PTR) port300BasePA.QuadPart;
        devExt->Port300Count = port300Count;
    }

    //
    // Map in the 718-720 port IO Space resource.
    //
    if (devExt->Port718Mapped) {

        devExt->Port718Base =
            (PUCHAR) MmMapIoSpace( port718BasePA, port718Count, MmNonCached );

        if (!devExt->Port718Base) {

            TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_INIT,
                                "Unable to map port range [%X-%X]",
                                (ULONG) (ULONG_PTR) devExt->Port718Base,
                                (ULONG) devExt->Port718Count );

            return STATUS_INSUFFICIENT_RESOURCES;
        }

        devExt->Port718Count = port718Count;

    } else {

        devExt->Port718Base  = (PUCHAR)(ULONG_PTR) port718BasePA.QuadPart;
        devExt->Port718Count = port718Count;
    }

    return STATUS_SUCCESS;
}

NTSTATUS
AmccIsaEvtDeviceReleaseHardware(
    __in WDFDEVICE       Device,
    __in WDFCMRESLIST   ResourcesTranslated
    )
/*++

Routine Description:

    EvtDeviceReleaseHardware is called by the framework in response to
    IRP_MN_STOP_DEVICE and IRP_MN_REMOVE_DEVICE from the PnP manager.

Arguments:

    Device - Handle to a framework device object.
    ResourcesTranslated - WDF Collection of resources.

Return Value:

    NTSTATUS

--*/
{
    PRDK_DEVICE_EXTENSION   devExt = NULL;

    UNREFERENCED_PARAMETER(ResourcesTranslated);

    PAGED_CODE();

    devExt = AmccIsaGetDevExt(Device);

    //
    // Unmap the IO resource
    //
    if (devExt->Port300Base) {

        MmUnmapIoSpace( devExt->Port300Base, devExt->Port300Count );

        devExt->Port300Base = NULL;
    }

    if (devExt->Port718Base) {

        MmUnmapIoSpace( devExt->Port718Base, devExt->Port718Count );

        devExt->Port718Base = NULL;
    }

    return STATUS_SUCCESS;
}

VOID
AmccIsaEvtIoDeviceControl(
    __in WDFQUEUE      Queue,
    __in WDFREQUEST    Request,
    __in size_t         OutputBufferLength,
    __in size_t         InputBufferLength,
    __in ULONG         IoControlCode
    )
/*++
Routine Description:

    This event is called when the framework receives IRP_MJ_DEVICE_CONTROL
    requests from the system.

Arguments:

    Queue - Handle to the framework queue object that is associated
            with the I/O request.
    Request - Handle to a framework request object.

    OutputBufferLength - length of the request's output buffer,
                        if an output buffer is available.
    InputBufferLength - length of the request's input buffer,
                        if an input buffer is available.

    IoControlCode - the driver-defined or system-defined I/O control code
                    (IOCTL) that is associated with the request.
Return Value:

    VOID

--*/
{
    PRDK_DEVICE_EXTENSION   devExt;
    size_t                  length = 0;
    NTSTATUS                status = STATUS_SUCCESS;
    PVOID                   buffer = NULL;

    PAGED_CODE();

    UNREFERENCED_PARAMETER( InputBufferLength  );
    UNREFERENCED_PARAMETER( OutputBufferLength  );

    //
    // Get the device extension.
    //
    devExt = AmccIsaGetDevExt(WdfIoQueueGetDevice( Queue ));

    //
    // Handle this request's specific code.
    //
    switch (IoControlCode) {

    case IOCTL_GET_VERSION:          // code == 0x800
    {

        status = WdfRequestRetrieveOutputBuffer(Request, sizeof(ULONG), &buffer, &length);
        if( !NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, AMCC_TRACE_IO,
                            "WdfRequestRetrieveInputBuffer failed 0x%x\n", status);
            break;
        }


        *(PULONG) buffer = 0x0004000A;

        status = STATUS_SUCCESS;
        length = sizeof(ULONG);
        break;
    }

    case IOCTL_RESET:               // code == 0x801
    {
        status = AmccIsaResetDevice( devExt );
        break;
    }

    case IOCTL_READ_DMA:        // code == 0x804
    case IOCTL_WRITE_DMA:       // code == 0x805
    {
        BOOLEAN  isRead = (IoControlCode == IOCTL_READ_DMA) ? TRUE:FALSE;

        status = AmccIsaReadWriteDma( devExt, Request, isRead );
        break;
    }

    default:
        status = STATUS_INVALID_DEVICE_REQUEST;
        break;
    }

    if (status != STATUS_PENDING) {

        WdfRequestCompleteWithInformation(Request, status, length);
    }
}



NTSTATUS
AmccIsaResetDevice(
    __in PRDK_DEVICE_EXTENSION  DevExt
    )
/*++

Routine Description:

Arguments:

Return Value:

--*/
{
    UNREFERENCED_PARAMETER(DevExt);

    PAGED_CODE();

    //
    // Reset the device by writing "1" bits to the reset flags in the general
    // control/status register. It's not necessary to reset any of these bits
    // to "0" afterwards. Other bits in GCSTS are R/O and unaffected by this
    //
    #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")
    WRITE_PORT_ULONG(AGCSTS, GCSTS_RESET);

    //
    // Disable all add-on interrupts
    //
    #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")
    WRITE_PORT_ULONG(AINT, INT_INTERRUPT_MASK);

    return STATUS_SUCCESS;
}


NTSTATUS
AmccIsaReadWriteDma(
    __in PRDK_DEVICE_EXTENSION  DevExt,
    __in WDFREQUEST         Request,
    __in BOOLEAN            isRead
    )
/*++

Routine Description:

Arguments:

Return Value:

   NTSTATUS

   The request will not be completed if STATUS_PENDING is returned.

--*/
{
    NTSTATUS               status;
    size_t                  nbytes;
    PWORKITEM              item;
    WDF_OBJECT_ATTRIBUTES  attributes;
    WDF_WORKITEM_CONFIG    workitemConfig;
    WORKITEM_CALLBACK      callback;
    PVOID               buffer;

    //
    // Framework will automatically complete zero length requests, so let us make sure
    // that buffer is atleast 4-bytes long.
    //
    status = WdfRequestRetrieveOutputBuffer(Request, 4, &buffer, &nbytes);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    //
    // Byte count must be an integral of dwords
    //
    if (nbytes & 3) {
        return STATUS_INVALID_PARAMETER;
    }

    //
    // Create a WDFWORKITEM object on which the request
    // will be executed.
    //
    ASSERT(DevExt->Worker == NULL);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, WORKITEM);
    attributes.ParentObject = DevExt->Device;

    WDF_WORKITEM_CONFIG_INIT(&workitemConfig, AmccIsaWorker);

    status = WdfWorkItemCreate( &workitemConfig,
                                &attributes,
                                &DevExt->Worker );

    if (!NT_SUCCESS(status)) {
        return status;
    }

    item = AmccIsaGetWorkItem( DevExt->Worker );

    //
    // Set the parameters to this WorkItem
    //
    item->u.DmaWork.Buffer = buffer;

    callback = (isRead) ? (WORKITEM_CALLBACK) ReadDmaCallback :
                          (WORKITEM_CALLBACK) WriteDmaCallback;

    item->u.DmaWork.nbytes   = nbytes;
    item->u.DmaWork.numxfer  = 0;
    item->Request          = Request;
    item->Callback         = callback;
    item->DevExt           = DevExt;

    //
    // Execute this work item.
    //
    WdfWorkItemEnqueue( DevExt->Worker );

    //
    // Request will be completed in the work thread.
    //
    return STATUS_PENDING;

}


VOID
AmccIsaWorker(
    __in  WDFWORKITEM WorkItem
    )
/*++

Routine Description:

Arguments:

Return Value:

--*/
{
    PWORKITEM  item;

    PAGED_CODE();

    item = AmccIsaGetWorkItem( WorkItem );

    //
    // Call callback until it returns FALSE
    // (e.g TRUE == more processing needed).
    //
    do {} while ((*item->Callback)( item ));

    //
    // Delete this thread
    //
    item->DevExt->Worker = NULL;

    WdfObjectDelete( WorkItem );
}



BOOLEAN
ReadDmaCallback(
    __in PWORKITEM  Item
    )
/*++

Routine Description:

Arguments:

Return Value:

--*/
{
    ULONG  gcsts;
    ULONG  numxfer;
    ULONG  loops = 0;
    LARGE_INTEGER delay;

    //
    // The ISA side of this hardware evaluation board is a non-DMA,
    // non-interrupt PIO device.
    //
    // This type of hardware is not recommended because of the impact
    // on system performance since the I/O ready status must be
    // polled, and the transfer done by the processor when ready.
    //
    // Doing this kind of work within a kernel mode thread
    // (from the WDFWORKITEM) has an impact on the systems performance.
    //
    // To mitigate this, the following is done:
    //
    // - Ensure that when copying a large amount of data in the main transfer
    //   loop we check for I/O request cancelation are reasonable intervals.
    //
    // - When polling for ready data, yield the processor during
    //   intervals when data is not readily available, and only
    //   transfer data in the main loop when a previous data
    //   item was available immediately.
    //

    while (Item->u.DmaWork.nbytes) {

        //
        // This is the body of the main data transfer loop. We
        // will only retrieve data in this loop when it is immediately
        // available as indicated by the status register.
        //

        //
        // See if there is any data available to be read.
        // read general control/status register
        //
        #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")
        gcsts = READ_PORT_ULONG( AGCSTS );

        if (gcsts & GCSTS_RFIFO_EMPTY) {
            break; // no data to read.
        }

        switch (Item->u.DmaWork.nbytes) {

            case 1:
            #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")   
            *Item->u.DmaWork.Buffer = READ_PORT_UCHAR((PUCHAR) AFIFO);
            numxfer = 1;
            break;

            case 2:
            #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values") 
            ((PUSHORT) (Item->u.DmaWork.Buffer))[0] =
                READ_PORT_USHORT((PUSHORT) AFIFO);
            numxfer = 2;
            break;

            case 3:
            #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")   
            READ_PORT_BUFFER_UCHAR((PUCHAR) AFIFO, Item->u.DmaWork.Buffer, 3);
            numxfer = 3;
            break;

            default:
            #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")    
            ((PULONG) (Item->u.DmaWork.Buffer))[0] =
                READ_PORT_ULONG(AFIFO);
            numxfer = 4;
            break;

        }

        Item->u.DmaWork.Buffer  += numxfer;
        Item->u.DmaWork.numxfer += numxfer;
        Item->u.DmaWork.nbytes  -= numxfer;

        //
        // If we are doing a large block transfer, we want to
        // check for I/O cancellation at reasonable intervals.
        //
        // This value is determined by the hardware itself. Currently,
        // the count of 512 is arbitrary.
        //
        if (loops++ > 512) {
            if (WdfRequestIsCanceled(Item->Request)) {
               WdfRequestComplete( Item->Request, STATUS_CANCELLED);
               return FALSE;
            }
        }
    }

    if (Item->u.DmaWork.nbytes == 0) {

        TraceEvents(TRACE_LEVEL_INFORMATION, AMCC_TRACE_IO,
                            "numxfer %I64d", Item->u.DmaWork.numxfer);

        WdfRequestCompleteWithInformation( Item->Request,
                                           STATUS_SUCCESS,
                                           Item->u.DmaWork.numxfer );

        return FALSE;   // Operation completed.
    }

    //
    // Data was not ready immediately, so we will check for
    // request cancellation, and if the request is not cancelled,
    // yield the processor before attempting to continue with
    // the transfer loop.
    //

    if (WdfRequestIsCanceled(Item->Request)) {
       //
       // Cancel the polling and exit.
       //
       WdfRequestComplete( Item->Request, STATUS_CANCELLED);
       return FALSE;
    }

    //
    // Yield the processor so we do not take up to much time.
    //
    // This gives other applications and threads a chance to
    // respond. In this case, the application thread feeding
    // data to the other side of this card may be a lower
    // priority than the system workitem, and without this
    // delay we can starve the thread not giving it a chance
    // to setup the transfer and provide the data.
    //
    // Note: Do not use KeStallExecutionProcessor since this is
    //       a spinloop, and will not yield the current thread.
    //

    delay.QuadPart = WDF_REL_TIMEOUT_IN_MS(10);
    KeDelayExecutionThread(KernelMode, FALSE, &delay);

    return TRUE;        // Operation not complete yet.
}


BOOLEAN
WriteDmaCallback(
    __in PWORKITEM  Item
    )
/*++

Routine Description:

Arguments:

Return Value:

--*/
{
    ULONG    gcsts;
    ULONG    numxfer;

    while (Item->u.DmaWork.nbytes) {

        // See if there's room to write more data. Note that the S5933 always
        // moves data in 32-bit chunks through the FIFO, so finding the FIFO
        // non-full means there's room for at least 4 bytes.

        //
        // read general control/status register
        //
        #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")    
        gcsts = READ_PORT_ULONG(AGCSTS);

        if (gcsts & GCSTS_WFIFO_FULL) {
            break; // no room in FIFO for more data
        }

        switch (Item->u.DmaWork.nbytes) {

            case 1:
            #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")   
            WRITE_PORT_UCHAR((PUCHAR) AFIFO, *Item->u.DmaWork.Buffer);
            numxfer = 1;
            break;

            case 2:
            #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")                   
            WRITE_PORT_USHORT((PUSHORT) AFIFO,
                              ((PUSHORT) (Item->u.DmaWork.Buffer))[0]);
            numxfer = 2;
            break;

            case 3:
            #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")    
            WRITE_PORT_BUFFER_UCHAR((PUCHAR) AFIFO, Item->u.DmaWork.Buffer, 3);
            numxfer = 3;
            break;

            default:
            #pragma prefast(suppress: 28138, "Legacy ISA device so we use hardcoded values")    
            WRITE_PORT_ULONG(AFIFO, ((PULONG) (Item->u.DmaWork.Buffer))[0]);
            numxfer = 4;
            break;
        }                   // write some data bytes

        Item->u.DmaWork.Buffer  += numxfer;
        Item->u.DmaWork.numxfer += numxfer;
        Item->u.DmaWork.nbytes  -= numxfer;

        if(WdfRequestIsCanceled(Item->Request)) {
            //
            // Abort the polling and exit.
            //
                WdfRequestComplete( Item->Request, STATUS_CANCELLED);
            return FALSE;
        }
    }

    if (!Item->u.DmaWork.nbytes) {

        TraceEvents(TRACE_LEVEL_INFORMATION, AMCC_TRACE_IO,
                       "numxfer %I64d", Item->u.DmaWork.numxfer);

        WdfRequestCompleteWithInformation( Item->Request,
                                           STATUS_SUCCESS,
                                           Item->u.DmaWork.numxfer );

        return FALSE;   // Operation completed.
    }

    return TRUE;        // Operation not complete yet
}



